/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.skatiger.jrove.util;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public final class Classpath {

    /**
     * For URLs to JARs that do not use JarURLConnection - allowed by the
     * servlet spec - attempt to produce a JarFile object all the same. Known
     * servlet engines that function like this include Weblogic and OC4J. This
     * is not a full solution, since an unpacked WAR or EAR will not have JAR
     * "files" as such.
     */
    private static JarFile getAlternativeJarFile(URL url) throws IOException {
        String urlFile = url.getFile();
        // Trim off any suffix - which is prefixed by "!/" on Weblogic
        int separatorIndex = urlFile.indexOf("!/");

        // OK, didn't find that. Try the less safe "!", used on OC4J
        if (separatorIndex == -1) {
            separatorIndex = urlFile.indexOf('!');
        }

        if (separatorIndex != -1) {
            String jarFileUrl = urlFile.substring(0, separatorIndex);
            // And trim off any "file:" prefix.
            if (jarFileUrl.startsWith("file:")) {
                jarFileUrl = jarFileUrl.substring("file:".length());
                jarFileUrl = URLDecoder.decode(jarFileUrl, "UTF-8");
            }
            return new JarFile(jarFileUrl);
        }
        return null;
    }

    /**
     * Open input stream from url. Ignore any errors.
     * 
     * @param url
     *            the url to open
     * @return input stream or null if not possible
     */
    private static InputStream getInputStream(URL url) {
        try {
            return url.openStream();
        } catch (Throwable t) {
            return null;
        }
    }

    /**
     * Join tokens, exlude last if param equals true.
     * 
     * @param tokens
     *            the tokens
     * @param excludeLast
     *            do we exclude last token
     * @return joined tokens
     */
    private static String join(String[] tokens, boolean excludeLast) {
        StringBuffer join = new StringBuffer();
        for (int i = 0; i < tokens.length - (excludeLast ? 1 : 0); i++) {
            join.append(tokens[i]).append("/");
        }
        return join.toString();
    }

    public static URL[] search(ClassLoader cl, String prefix, String suffix)
            throws IOException {
        @SuppressWarnings("unchecked")
        Enumeration<URL>[] e = new Enumeration[] { cl.getResources(prefix),
                cl.getResources(prefix + "MANIFEST.MF") };
        Set<URL> all = new LinkedHashSet<URL>();
        for (int i = 0, s = e.length; i < s; ++i) {
            while (e[i].hasMoreElements()) {
                URL url = (URL) e[i].nextElement();
                URLConnection conn = url.openConnection();
                JarFile jarFile;
                conn.setUseCaches(false);
                conn.setDefaultUseCaches(false);
                if (conn instanceof JarURLConnection) {
                    jarFile = ((JarURLConnection) conn).getJarFile();
                } else {
                    jarFile = getAlternativeJarFile(url);
                }
                if (jarFile != null) {
                    searchJar(cl, all, jarFile, prefix, suffix);
                } else {
                    boolean searchDone = searchDir(
                            all,
                            new File(URLDecoder.decode(url.getFile(), "UTF-8")),
                            suffix);
                    if (!searchDone) {
                        searchFromURL(all, prefix, suffix, url);
                    }
                }
            }
        }
        URL[] urlArray = (URL[]) all.toArray(new URL[all.size()]);
        return urlArray;
    }

    public static URL[] search(String prefix, String suffix) throws IOException {
        return search(Thread.currentThread().getContextClassLoader(), prefix,
                suffix);
    }

    private static boolean searchDir(Set<URL> result, File file, String suffix)
            throws IOException {
        if (file.exists() && file.isDirectory()) {
            File[] fc = file.listFiles();
            String path;
            @SuppressWarnings("unused")
            URL src;
            for (int i = 0; i < fc.length; i++) {
                path = fc[i].getAbsolutePath();
                if (fc[i].isDirectory()) {
                    searchDir(result, fc[i], suffix);
                } else if (path.endsWith(suffix)) {
                    // result.add(new URL("file:/" + path));
                    result.add(fc[i].toURI().toURL());
                }
            }
            return true;
        }
        return false;
    }

    private static void searchFromURL(Set<URL> result, String prefix,
            String suffix, URL url) throws IOException {
        boolean done = false;
        InputStream is = getInputStream(url);
        if (is != null) {
            try {
                ZipInputStream zis;
                if (is instanceof ZipInputStream) {
                    zis = (ZipInputStream) is;
                } else {
                    zis = new ZipInputStream(is);
                }
                try {
                    ZipEntry entry = zis.getNextEntry();
                    // initial entry should not be null
                    // if we assume this is some inner jar
                    done = (entry != null);
                    while (entry != null) {
                        String entryName = entry.getName();
                        if (entryName.endsWith(suffix)) {
                            String urlString = url.toExternalForm();
                            result.add(new URL(urlString + entryName));
                        }
                        entry = zis.getNextEntry();
                    }
                } finally {
                    zis.close();
                }
            } catch (Exception ignore) {
            }
        }
        if (done == false && prefix.length() > 0) {
            // we add '/' at the end since join adds it as well
            String urlString = url.toExternalForm() + "/";
            String[] split = prefix.split("/");
            prefix = join(split, true);
            String end = join(split, false);
            int p = urlString.lastIndexOf(end);
            url = new URL(urlString.substring(0, p));
            searchFromURL(result, prefix, suffix, url);
        }
    }

    private static void searchJar(ClassLoader cl, Set<URL> result,
            JarFile file, String prefix, String suffix) throws IOException {
        Enumeration<JarEntry> e = file.entries();
        JarEntry entry;
        String name;
        while (e.hasMoreElements()) {
            try {
                entry = e.nextElement();
            } catch (Throwable t) {
                continue;
            }
            name = entry.getName();
            if (name.startsWith(prefix) && name.endsWith(suffix)) {
                Enumeration<URL> e2 = cl.getResources(name);
                while (e2.hasMoreElements()) {
                    result.add(e2.nextElement());
                }
            }
        }
    }

    /**
     * 
     */
    public Classpath() {
        super();
    }

}
